/*
 * This file is part of Dependency-Track.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 * SPDX-License-Identifier: Apache-2.0
 * Copyright (c) OWASP Foundation. All Rights Reserved.
 */
package org.dependencytrack.persistence;

import org.dependencytrack.PersistenceCapableTest;
import org.dependencytrack.model.Vulnerability;
import org.dependencytrack.model.VulnerabilityAlias;
import org.junit.jupiter.api.Test;
import org.junit.experimental.runners.Enclosed;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import org.junit.runner.RunWith;

import javax.jdo.Query;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.Calendar;
import java.util.List;
import java.util.function.Consumer;

import static java.util.Collections.singletonList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.dependencytrack.persistence.VulnerabilityQueryManagerTest.SynchronizeVulnerabilityAliasTest.VulnerabilityAliasBuilder.anAlias;

@RunWith(Enclosed.class)
public class VulnerabilityQueryManagerTest {

    @Nested
    class SynchronizeVulnerabilityAliasTest extends PersistenceCapableTest {

        @Test
        void updateVulnerabilityNoChangesInUpdated() {
            Vulnerability vuln = new Vulnerability();
            final Date updated = new Date();
            vuln.setId(124L);
            vuln.setVulnId("CVE-1234-123");
            vuln.setSource(Vulnerability.Source.NVD);
            vuln.setUpdated(updated);
            qm.persist(vuln);

            Vulnerability transientVuln = new Vulnerability();
            transientVuln.setId(vuln.getId());
            transientVuln.setVulnId("CVE-1234-123");
            transientVuln.setSource(Vulnerability.Source.GITHUB);
            transientVuln.setUpdated(updated); // same updated date

            Vulnerability result = qm.synchronizeVulnerability(transientVuln, false);
            assertThat(result).isNull();

            // check the database to see if the vulnerability was updated
            Vulnerability dbVuln = qm.getObjectById(Vulnerability.class, vuln.getId());
            assertThat(dbVuln.getUpdated()).isEqualTo(updated);
            assertThat(dbVuln.getSource()).isEqualTo("NVD");
        }

        @Test
        void testUpdateVulnerabilityExecutionTime() {
        Vulnerability vuln = new Vulnerability();
        final Date updated = new Date();
        vuln.setId(124L);
        vuln.setVulnId("CVE-1234-123");
        vuln.setSource(Vulnerability.Source.NVD);
        vuln.setUpdated(updated);
        qm.persist(vuln);
        System.out.println("Persisted: " + vuln.getUpdated());

        Calendar calendar = Calendar.getInstance();
        calendar.setTime(updated);
        calendar.add(Calendar.MONTH, 3); // Add 3 months
        final Date newUpdated = calendar.getTime();
        System.out.println("New Updated Date: " + newUpdated);

        Vulnerability transientVuln = new Vulnerability();
        transientVuln.setId(vuln.getId());
        transientVuln.setVulnId("CVE-1234-123");
        transientVuln.setSource(Vulnerability.Source.GITHUB); //Change Source to GITHUB
        transientVuln.setUpdated(newUpdated); // new updated date
        long startTime = System.nanoTime();
        qm.updateVulnerability(transientVuln, false);
        long endTime = System.nanoTime();

        long duration = endTime - startTime;
        System.out.println("Execution time for updateVulnerability: " + duration + " ns");

        // Optionally, use assertions to check if the duration is below a threshold (e.g., under 1 ms)
        //assertTrue(duration < 200_000_000L, "Execution time is too high");
        }

        @Test
        void updateVulnerabilitySourceChanges() {
            Vulnerability vuln = new Vulnerability();
            final Date updated = new Date();
            vuln.setId(124L);
            vuln.setVulnId("CVE-1234-123");
            vuln.setSource(Vulnerability.Source.NVD);
            vuln.setUpdated(updated);
            qm.persist(vuln);
            System.out.println("Persisted: " + vuln.getUpdated());

            Calendar calendar = Calendar.getInstance();
            calendar.setTime(updated);
            calendar.add(Calendar.MONTH, 3); // Add 3 months
            final Date newUpdated = calendar.getTime();
            System.out.println("New Updated Date: " + newUpdated);

            Vulnerability transientVuln = new Vulnerability();
            transientVuln.setId(vuln.getId());
            transientVuln.setVulnId("CVE-1234-123");
            transientVuln.setSource(Vulnerability.Source.GITHUB); //Change Source to GITHUB
            transientVuln.setUpdated(newUpdated); // new updated date
            Vulnerability result = qm.synchronizeVulnerability(transientVuln, false);
            assertThat(result).isNotNull();


            // check the database to see if the vulnerability was updated
            Vulnerability dbVuln = qm.getObjectById(Vulnerability.class, vuln.getId());
            assertThat(dbVuln.getUpdated()).isEqualTo(newUpdated);
            assertThat(dbVuln.getSource()).isEqualTo("GITHUB");
        }

        @ParameterizedTest
        @MethodSource("synchronizeVulnerabilityAliasTestParams")
        @SuppressWarnings({"unchecked", "resource"})
        void synchronizeVulnerabilityAliasTest(final String description,
                                               final List<VulnerabilityAlias> reportedAliases,
                                               final List<VulnerabilityAlias> expectedAliases) {
            for (final VulnerabilityAlias reportedAlias : reportedAliases) {
                qm.synchronizeVulnerabilityAlias(reportedAlias);
            }

            final var aliasAsserts = new ArrayList<Consumer<VulnerabilityAlias>>();
            for (final VulnerabilityAlias expectedAlias : expectedAliases) {
                aliasAsserts.add(alias -> {
                    assertThat(alias.getCveId()).isEqualTo(expectedAlias.getCveId());
                    assertThat(alias.getGhsaId()).isEqualTo(expectedAlias.getGhsaId());
                    assertThat(alias.getGsdId()).isEqualTo(expectedAlias.getGsdId());
                    assertThat(alias.getInternalId()).isEqualTo(expectedAlias.getInternalId());
                    assertThat(alias.getOsvId()).isEqualTo(expectedAlias.getOsvId());
                    assertThat(alias.getSnykId()).isEqualTo(expectedAlias.getSnykId());
                    assertThat(alias.getSonatypeId()).isEqualTo(expectedAlias.getSonatypeId());
                    assertThat(alias.getVulnDbId()).isEqualTo(expectedAlias.getVulnDbId());
                });
            }

            final Query<VulnerabilityAlias> query = qm.getPersistenceManager().newQuery(VulnerabilityAlias.class);
            final List<VulnerabilityAlias> aliases = query.executeList();

            assertThat(aliases).as(description).satisfiesExactlyInAnyOrder(aliasAsserts.toArray(new Consumer[0]));
        }

        @SuppressWarnings("unused")
        private static Object[] synchronizeVulnerabilityAliasTestParams() {
            return new Object[]{
                    new Object[]{
                            "Aliases must not be merged when identifiers do not match",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-2000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build()),
                            // Expected
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-2000")
                                            .build())
                    },
                    new Object[]{
                            "Must merge with best match among existing aliases",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-2000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-2000")
                                            .withSnykId("SNYK-1000")
                                            .build()),
                            // Expected
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-2000")
                                            .withSnykId("SNYK-1000")
                                            .build())
                    },
                    new Object[]{
                            "Aliases with disjoint sets of identifiers must not be merged",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-1000")
                                            .withOsvId("GO-1000")
                                            .build()),
                            // Expected
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-1000")
                                            .withOsvId("GO-1000")
                                            .build()),
                    },
                    new Object[]{
                            "Aliases with disjoint sets of identifiers must not be merged when arriving in reverse order",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withGhsaId("GHSA-1000")
                                            .withOsvId("GO-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build()),
                            // Expected
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-1000")
                                            .withOsvId("GO-1000")
                                            .build()),
                    },
                    new Object[]{
                            "Aliases with one matching identifier must be merged",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build()),
                            // Expected
                            singletonList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .withOsvId("GO-1000")
                                            .build())
                    },
                    new Object[]{
                            "Aliases with one matching identifier must be merged when arriving in reverse order",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-1000")
                                            .build()),
                            // Expected
                            singletonList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .withOsvId("GO-1000")
                                            .build())
                    },
                    new Object[]{
                            "Aliases with one matching identifier and multiple additional identifiers must be merged",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withOsvId("GO-1000")
                                            .build()),
                            // Expected
                            singletonList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-1000")
                                            .withOsvId("GO-1000")
                                            .build())
                    },
                    new Object[]{
                            "Aliases with one matching identifier and multiple additional identifiers must be merged when arriving in reverse order",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withOsvId("GO-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build()),
                            // Expected
                            singletonList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-1000")
                                            .withOsvId("GO-1000")
                                            .build())
                    },
                    new Object[]{
                            "Aliases with two matching identifiers must be merged",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withOsvId("GO-1000")
                                            .build()),
                            // Expected
                            singletonList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-1000")
                                            .withOsvId("GO-1000")
                                            .build())
                    },
                    new Object[]{
                            "Aliases with two matching identifiers must be merged when arriving in reverse order",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withOsvId("GO-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-1000")
                                            .build()),
                            // Expected
                            singletonList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-1000")
                                            .withOsvId("GO-1000")
                                            .build())
                    },
                    new Object[]{
                            "Aliases with one matching and one conflicting identifier must not be merged",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-2000")
                                            .build()),
                            // Expected
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withOsvId("GO-2000")
                                            .build())
                    },
                    new Object[]{
                            "In-the-wild example: GO-2022-0586",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-2022-26945")
                                            .withOsvId("GO-2022-0586")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-30321")
                                            .withOsvId("GO-2022-0586")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-30322")
                                            .withOsvId("GO-2022-0586")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-30323")
                                            .withOsvId("GO-2022-0586")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-28r2-q6m8-9hpx")
                                            .withOsvId("GO-2022-0586")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-cjr4-fv6c-f3mv")
                                            .withOsvId("GO-2022-0586")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-fcgg-rvwg-jv58")
                                            .withOsvId("GO-2022-0586")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-x24g-9w7v-vprh")
                                            .withOsvId("GO-2022-0586")
                                            .build()),
                            // Expected
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-2022-26945")
                                            .withOsvId("GO-2022-0586")
                                            .withGhsaId("GHSA-28r2-q6m8-9hpx")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-30321")
                                            .withGhsaId("GHSA-cjr4-fv6c-f3mv")
                                            .withOsvId("GO-2022-0586")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-30322")
                                            .withGhsaId("GHSA-fcgg-rvwg-jv58")
                                            .withOsvId("GO-2022-0586")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-30323")
                                            .withGhsaId("GHSA-x24g-9w7v-vprh")
                                            .withOsvId("GO-2022-0586")
                                            .build())
                    },
                    new Object[]{
                            "Aliases with identical identifiers must not create duplicates",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build()),
                            // Expected
                            singletonList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build()),
                    },
                    new Object[]{
                            "In-the-wild example: SNYK-JAVA-ORGECLIPSEJETTY-2945452 and SNYK-JAVA-ORGECLIPSEJETTY-2945453",
                            // Reported
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-2000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withSnykId("SNYK-2000")
                                            .build()),
                            // Expected
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-1000")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-1000")
                                            .withGhsaId("GHSA-1000")
                                            .withSnykId("SNYK-2000")
                                            .build())
                    },
                    // NOTE: This test is meant to act as documentation of the solution's behavior
                    // in case ambiguous data is being reported by vulnerability sources.
                    // In this case, one Snyk vulnerability refers to multiple CVEs and GHSAs.
                    // We have no way of knowing which CVEs and GHSAs alias each other.
                    // Synchronization may cause unrelated CVEs and GHSAs to be correlated.
                    new Object[]{
                            "In-the-wild example: SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135",
                            // Reported
                            Arrays.asList(
                                    // Snyk reports various GHSAs and one CVE as aliases of SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135.
                                    anAlias()
                                            .withGhsaId("GHSA-9fwf-46g9-45rx")
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-3f7h-mf4q-vrm4")
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-5hc5-c3m9-8vcj")
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-fv22-xp26-mm9w")
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-4rv7-wj6m-6c6r")
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-40152")
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    // GitHub Advisories and / or OSV report relationships between CVEs and GHSAs.
                                    anAlias()
                                            .withCveId("CVE-2022-40152")
                                            .withGhsaId("GHSA-3f7h-mf4q-vrm4")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-40154")
                                            .withGhsaId("GHSA-9fwf-46g9-45rx")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-40156")
                                            .withGhsaId("GHSA-4rv7-wj6m-6c6r")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-40153")
                                            .withGhsaId("GHSA-fv22-xp26-mm9w")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-40155")
                                            .withGhsaId("GHSA-5hc5-c3m9-8vcj")
                                            .build()),
                            // Expected (what it should be)
//                            Arrays.asList(
//                                    anAlias()
//                                            .withCveId("CVE-2022-40152")
//                                            .withGhsaId("GHSA-3f7h-mf4q-vrm4")
//                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
//                                            .build(),
//                                    anAlias()
//                                            .withCveId("CVE-2022-40153")
//                                            .withGhsaId("GHSA-fv22-xp26-mm9w")
//                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
//                                            .build(),
//                                    anAlias()
//                                            .withCveId("CVE-2022-40154")
//                                            .withGhsaId("GHSA-9fwf-46g9-45rx")
//                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
//                                            .build(),
//                                    anAlias()
//                                            .withCveId("CVE-2022-40155")
//                                            .withGhsaId("GHSA-5hc5-c3m9-8vcj")
//                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
//                                            .build(),
//                                    anAlias()
//                                            .withCveId("CVE-2022-40156")
//                                            .withGhsaId("GHSA-4rv7-wj6m-6c6r")
//                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
//                                            .build())
                            // Expected (what it is)
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-2022-40152")
                                            .withGhsaId("GHSA-9fwf-46g9-45rx") // MISMATCH: Should be GHSA-3f7h-mf4q-vrm4
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-40152")
                                            .withGhsaId("GHSA-3f7h-mf4q-vrm4")
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-40155")
                                            .withGhsaId("GHSA-5hc5-c3m9-8vcj")
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-40153")
                                            .withGhsaId("GHSA-fv22-xp26-mm9w")
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-40156")
                                            .withGhsaId("GHSA-4rv7-wj6m-6c6r")
                                            .withSnykId("SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2022-40154")
                                            .withGhsaId("GHSA-9fwf-46g9-45rx")
                                            // Should have SNYK-JAVA-COMFASTERXMLWOODSTOX-3091135
                                            .build())
                    },
                    new Object[]{
                            "In-the-wild example: SNYK-JAVA-IONETTY-10822*",
                            // Reported
                            Arrays.asList(
                                    // Multiple SNYK vulnerabilities refer to the same CVE and GHSA pair.
                                    anAlias()
                                            .withCveId("CVE-2021-21290")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082234")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-5mcr-gq6c-3hq2")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082234")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2021-21290")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082235")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-5mcr-gq6c-3hq2")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082235")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2021-21290")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082236")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-5mcr-gq6c-3hq2")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082236")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2021-21290")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082238")
                                            .build(),
                                    anAlias()
                                            .withGhsaId("GHSA-5mcr-gq6c-3hq2")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082238")
                                            .build(),
                                    // GitHub Advisories and / or OSV report the CVE and GHSA to be aliases.
                                    anAlias()
                                            .withCveId("CVE-2021-21290")
                                            .withGhsaId("GHSA-5mcr-gq6c-3hq2")
                                            .build()),
                            // Expected
                            Arrays.asList(
                                    anAlias()
                                            .withCveId("CVE-2021-21290")
                                            .withGhsaId("GHSA-5mcr-gq6c-3hq2")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082234")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2021-21290")
                                            .withGhsaId("GHSA-5mcr-gq6c-3hq2")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082235")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2021-21290")
                                            .withGhsaId("GHSA-5mcr-gq6c-3hq2")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082236")
                                            .build(),
                                    anAlias()
                                            .withCveId("CVE-2021-21290")
                                            .withGhsaId("GHSA-5mcr-gq6c-3hq2")
                                            .withSnykId("SNYK-JAVA-IONETTY-1082238")
                                            .build())
                    }
            };
        }

        static class VulnerabilityAliasBuilder {
            private final VulnerabilityAlias alias = new VulnerabilityAlias();

            static VulnerabilityAliasBuilder anAlias() {
                return new VulnerabilityAliasBuilder();
            }

            VulnerabilityAlias build() {
                return alias;
            }

            VulnerabilityAliasBuilder withCveId(final String cveId) {
                this.alias.setCveId(cveId);
                return this;
            }

            VulnerabilityAliasBuilder withGhsaId(final String ghsaId) {
                this.alias.setGhsaId(ghsaId);
                return this;
            }

            VulnerabilityAliasBuilder withOsvId(final String osvId) {
                this.alias.setOsvId(osvId);
                return this;
            }

            VulnerabilityAliasBuilder withSnykId(final String snykId) {
                this.alias.setSnykId(snykId);
                return this;
            }
        }

    }

}